package com.gitee.wsl.jvm.okhttp

import com.burgstaller.okhttp.AuthenticationCacheInterceptor
import com.burgstaller.okhttp.CachingAuthenticatorDecorator
import com.burgstaller.okhttp.DispatchingAuthenticator
import com.burgstaller.okhttp.basic.BasicAuthenticator
import com.burgstaller.okhttp.digest.CachingAuthenticator
import com.burgstaller.okhttp.digest.Credentials
import com.burgstaller.okhttp.digest.DigestAuthenticator
import com.gitee.wsl.jvm.net.security.token.AuthenticationResponse
import com.gitee.wsl.jvm.net.security.token.TokenManager
import com.gitee.wsl.jvm.net.security.token.TokenStorage
import com.gitee.wsl.jvm.okhttp.OkHttpExt.createSSLSocketFactory
import com.gitee.wsl.jvm.okhttp.intercept.auth.AuthInterceptor
import com.gitee.wsl.jvm.okhttp.intercept.auth.UserAgentInterceptor
import com.gitee.wsl.jvm.okhttp.intercept.base.CacheHeaderSettingInterceptor
import com.gitee.wsl.jvm.okhttp.intercept.base.GzipRequestInterceptor
import com.gitee.wsl.jvm.okhttp.intercept.base.HeadersInterceptor
import com.gitee.wsl.jvm.okhttp.intercept.base.InjectParamsInterceptor
import com.gitee.wsl.jvm.okhttp.intercept.updown.ProgressDownLoadInterceptor
import com.gitee.wsl.jvm.okhttp.listener.ProgressListener
import com.gitee.wsl.jvm.okhttp.intercept.store.StaleIfErrorInterceptor
import com.gitee.wsl.jvm.okhttp.intercept.trace.TraceIdInterceptor
import com.gitee.wsl.jvm.okhttp.intercept.trace.TraceIdProvider
import okhttp3.Cache
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.brotli.BrotliInterceptor
import okhttp3.logging.HttpLoggingInterceptor
import java.io.File
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.TreeMap
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit

interface OkHttpClientScope{

    fun getBuilder(): OkHttpClient.Builder

    fun config(config: OkHttpClient.Builder.()->Unit)

    fun userAgent(userAgentString: String)

    fun trustAllCerts()

    fun ableDebug(level: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.HEADERS)

    fun ableCache(cacheDir: File , cacheSize:Long)

    fun cacheTimeInterval(maxTimeStale:Int= CacheHeaderSettingInterceptor.MAX_STALE,
                          maxTimeStaleOnline:Int= CacheHeaderSettingInterceptor.MAX_STALE_ONLINE)

    fun ableGzipRequest()

    fun onProgress(listener: ProgressListener)

    fun header(headerConfig: Headers.Builder.()->Unit)

    fun useProxy(proxyAddress:String,proxyPort:Int,authUser:String?=null,authPass:String?=null)

    fun login(authUser:String, authPass:String, loginType: LoginType = LoginType.BASE)

    //fun useCronetTransport()

    //解码
    //头部Content-Encoding 为gzip , br 时
    fun ableBrotliResponse()


    //扩展类
    fun staleIfError(staleDuration: Int = StaleIfErrorInterceptor.DEFULAT_MAX_STALE_DURATION,
                     staleDurationTimeUnit: TimeUnit = StaleIfErrorInterceptor.DEFULAT_MAX_STALE_TIMEUNIT)

    fun injectParams(injectFun: (paramMap: TreeMap<String, String>)-> TreeMap<String, String>)

    fun trace(traceName:String, traceIdProvider: TraceIdProvider)

    fun auth(tokenStorage: TokenStorage, refreshToken: (request: Request)->Result<AuthenticationResponse>)
}


fun okHttpClient(config:OkHttpClientScope.()->Unit):OkHttpClient{
    val builder = OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .callTimeout(1, TimeUnit.MINUTES)

    val scope = object:OkHttpClientScope{
        override fun getBuilder(): OkHttpClient.Builder = builder

        override fun config(config: OkHttpClient.Builder.() -> Unit) {
            config(builder)
        }


        override fun userAgent(userAgentString: String) {
            builder.addInterceptor(UserAgentInterceptor(userAgentString))
        }

        override fun trustAllCerts() {
            builder.sslSocketFactory(createSSLSocketFactory(),TrustAllCerts())
            builder.hostnameVerifier(createInsecureHostnameVerifier())
        }

        override fun header(headerConfig: Headers.Builder.() -> Unit) {
            val headers= Headers.Builder()
            headerConfig(headers)
            builder.addInterceptor(HeadersInterceptor(headers.build()))
        }

        override fun ableDebug(level:HttpLoggingInterceptor.Level) {
            val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
                this.level = level
            }
            builder.addNetworkInterceptor(httpLoggingInterceptor)
        }

        override fun ableCache(cacheDir:File,cacheSize: Long) {
            builder.cache(Cache(cacheDir,cacheSize))
        }

        override fun cacheTimeInterval(maxTimeStale: Int, maxTimeStaleOnline: Int) {
            builder.addInterceptor(CacheHeaderSettingInterceptor(maxTimeStale, maxTimeStaleOnline))
        }


        override fun ableGzipRequest() {
            builder.addInterceptor(GzipRequestInterceptor())
        }

        override fun onProgress(listener: ProgressListener) {
            builder.addInterceptor(ProgressDownLoadInterceptor(listener))
        }

        override fun useProxy(
            proxyAddress: String,
            proxyPort: Int,
            authUser: String?,
            authPass: String?
        ) {
            val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(proxyAddress, proxyPort))
            builder.proxy(proxy)
            authUser?.let {
                builder.proxyAuthenticator(BasicAuthenticator(Credentials(authUser, authPass?:"")))
            }
        }

        override fun login(authUser: String, authPass: String, loginType: LoginType) {
            val credentials = Credentials(authUser, authPass)
            val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
            when(loginType){
                LoginType.BASE->{
                    val authenticator= BasicAuthenticator(credentials)
                    builder.authenticator( CachingAuthenticatorDecorator(authenticator, authCache))
                        .addInterceptor( AuthenticationCacheInterceptor(authCache))
                }
                LoginType.DIGEST-> {
                    val authenticator= DigestAuthenticator(credentials)
                    builder.authenticator( CachingAuthenticatorDecorator(authenticator, authCache))
                        .addInterceptor( AuthenticationCacheInterceptor(authCache))
                }
                LoginType.BASE_OR_DIGEST-> {
                    val baseAuthenticator= BasicAuthenticator(credentials)
                    val digestAuthenticator= DigestAuthenticator(credentials)

                    // note that all auth schemes should be registered as lowercase!
                    val authenticator = DispatchingAuthenticator.Builder()
                        .with("digest", digestAuthenticator)
                        .with("basic", baseAuthenticator)
                        .build()
                    builder.authenticator( CachingAuthenticatorDecorator(authenticator, authCache))
                        .addInterceptor( AuthenticationCacheInterceptor(authCache))
                }

            }
        }

        override fun ableBrotliResponse() {
            builder.addInterceptor(BrotliInterceptor)
        }


        override fun staleIfError(staleDuration: Int, staleDurationTimeUnit: TimeUnit) {
            builder.addInterceptor(StaleIfErrorInterceptor(staleDuration, staleDurationTimeUnit))
        }

        override fun injectParams(injectFun: (paramMap: TreeMap<String, String>) -> TreeMap<String, String>) {
            builder.addInterceptor(object: InjectParamsInterceptor() {
                override fun updateDynamicParams(dynamicMap: TreeMap<String, String>): TreeMap<String, String> {
                    return injectFun(dynamicMap)
                }
            })
        }

        override fun trace(traceName: String, traceIdProvider: TraceIdProvider) {
            builder.addInterceptor(TraceIdInterceptor(traceName,traceIdProvider))
        }

        override fun auth(tokenStorage: TokenStorage, refreshToken: (request: Request) -> Result<AuthenticationResponse>) {
            builder.addInterceptor(AuthInterceptor(TokenManager(refreshToken,tokenStorage)))
        }
    }
    config(scope)
    return builder.build()
}